import { type } from 'os';
// 1 类型守护--------------------------------->
// 当我们在使用联合类型时，假如两个子类型 A 和 B 都是接口或类等时，我们预想的是要么是 A，要么是 B。但实际上，A&B 也是符合 A|B 的。
// 这个时候我们需要先用 in 关键字来判断是符合哪个类型再进行操作
interface A {
  name: string
};
interface B {
  age: number
}
let fn1 = function (c: A | B): void {
  if ('name' in c) {
    console.log('fn1----name:', c.name);
  } else {
    console.log('fn1----age:', c.age);
  }
  
};
fn1({
  name: 'zhangsan'
});
fn1({
  age: 11
});





// 2  结构类型检查 --------------------------------->
interface A1 {
  name: string
}
interface B1 {
  name: string
}
let a: A1 = {
  name: 'lisi'
};
let b: B1 = a;

// 避免方法一：
interface A2 {
  name: string
  age: 'a2'
}

interface B2 {
  name: string
  age: 'b2'
}

let a1: A2 = {
  name: 'lisi',
  age: 'a2'
}

// let b2: B2 = a1;

// 避免方法二： 交叉类型
type UserIdString = string & { readonly brand: unique symbol }
type OrderIdString = string & { readonly brand: unique symbol }

const getUniqueUserId = (id: string) => {
  return id as UserIdString
}

const getUniqueOrderId = (id: string) => {
  return id as OrderIdString
}

let uniqueUserId: UserIdString = getUniqueUserId('1')
// let uniqueOrderId: OrderIdString = uniqueUserId // error




// 3 keyof 将一个类型的属性名全部提取出来当做联合类型--------------------------------->
interface A4 {
  name: string
  age: number
  sex: string
};

type A5 = keyof A4;
const p1: A5 = 'age';




// 4 Record 属性映射

// 定义一个普通的对象类型
type myObject = Record<string, any>;

// 搭配联合类型用法
type keys = 'get' | 'post' | 'put';
type myObj1 = Record<keys, number>;

// 同样可以映射对象,让对象的每个属性都是一个拥有特定键值对的类型
interface A6 {
  name: string
  age: number,
  color: string
}

type B6 = Record<string, A6>;
let b6: B6 = {
  'key': {
    name: '1',
    age: 2,
    color: '2'
  }
}



// 5 Partial (部分的; 不完全的)--------------------------------->
// 定义一个Person接口
interface Person {
  name: string
  age: number
}

// 但是我们有可能数据是请求过来的，刚开始我们需要定义一个空对象，如下。
const person1: Person = {}
/**
  但是这样就加粗样式会出现报错，类型“{}”缺少类型“Person”中的以下属性: name, age。
  可能我们可以更改定义方式如下，但是有的时候我们不想破坏事先的定义，或者不能破坏
  interface Person {
    name?: string
    age?: number
  }
*/

/**
  那这个时候我们就可以用到typescript自带的高级类型 Partial，就相当于将上方接口所有属性变成可选的
  将我们需要定义的类型当做泛型传入Partial中，那么就相当于当前的类型里面的所有属性都是可选的
 */
  const person2: Partial<Person> = {} // 可以
  const person3: Partial<Person> = { name: 'xiaodu' } // 可以
const person4: Partial<Person> = { height: 1.88 } // 报错 “height”不在类型“Partial<Person>”中
  



// 6 Required（必须的）--------------------------------->
// 和Partial刚好相反，将一个定义中的属性全部变成必选参数

interface dog {
  name?: string
  age?: number
}

let dog1: Required<dog> = {
  name: '11',
  age: 10
}





// 7 Pick（选择） ts中可以选择一个原来的接口中一部分的属性定义--------------------------------->
type A7 = {
  name: string,
  color: number
}

type B7 = Pick<A7, 'name' | 'color'>



// 8 Readonly (意思是只读的)--------------------------------->

// ts中就是让一个定义中的所有属性都变成只读参数

type A8 = {
  name: string
  age: number
}

type A88 = Readonly<A8>;

let a88: A88 = {
  name: '09',
  age: 9
};
// a88.name = 's';


// 9 Exclude(排除)  --------------------------------->
// ts中可以排除 联合类型 中一部分的内容
// 注意Exclude是操作联合类型的


type A10 = "one" | "two" | "three";
type B10 = Exclude<A10, 'one' | 'three'>;


// 10 Omit (省略的)--------------------------------->

// ts中就是将接口或者类型的键值对删除一部分

interface A11 {
  name: string
  color: number
  dog: string
}

type B11 = Omit<A11, 'name'>



// 11 ReadonlyArray（只读数组）--------------------------------->
// 创建一个数组，数组中的索引不允许被修改
// 我们知道当我们使用const创建对象或者数组时，其实是可以修改其内部属性的，但是有的时候我们可能不需要其内部能够被修改

const arr: number[] = [1, 2, 3, 4, 5]
arr[0] = 3 

// 方法1：通过类型断言的方式
const arr1 = [1, 2, 3, 4] as const;
// arr1[0] = 2;


// 方法2：使用ReadonlyArray，需要传入一个泛型来约束数组中的索引类型
const arr2: ReadonlyArray<number> = [2, 3, 4, 5];
// arr2[2] = 9;



// as const 和 ReadonlyArray这两者的区别在哪里？
// 区别在于as const是深层次的，尽管数组内放的对象，对象内部数据也是不能被修改的。ReadonlyArray则是‘浅层’的。
